/*******************************************************************************
 * Copyright (c) 2006-2009 Nicolas Richeton.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors :
 *    Nicolas Richeton (nicolas.richeton@gmail.com) - initial API and implementation
 *******************************************************************************/

package org.eclipse.nebula.animation;

import org.eclipse.nebula.animation.effects.IEffect;
import org.eclipse.nebula.animation.effects.MoveScrollBarEffect;
import org.eclipse.nebula.animation.movement.IMovement;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;

/**
 * <p>
 * Allows to replace the default scrolling behavior by an animation effect.
 * </p>
 * 
 * <p>
 * Compatible with :
 * </p>
 * <ul>
 * <li>Shell</li>
 * <li>StyledText</li>
 * <li>Canvas</li>
 * <li>Gallery</li>
 * </ul>
 * 
 * 
 * @author Nicolas Richeton
 * 
 */
public class ScrollingSmoother {

	Scrollable component;

	ScrollBar verticalScrollBar;

	ScrollBar horizontalScrollBar;

	IMovement movement = null;

	int duration = 2000;

	AnimationRunner animationRunner = new AnimationRunner();

	/**
	 * Create a Scrolling Smoother instance over a scrollable widget. This
	 * effect can then be activated using
	 * {@link ScrollingSmoother#smoothControl(boolean)}.
	 * 
	 * @see ScrollingSmoother#smoothControl(boolean)
	 * 
	 * @param c2
	 * @param movement
	 */
	public ScrollingSmoother(final Scrollable c2, IMovement movement) {
		this.component = c2;
		verticalScrollBar = c2.getVerticalBar();
		horizontalScrollBar = c2.getHorizontalBar();
		this.movement = movement;
	}

	/**
	 * Create a Scrolling Smoother instance over a scrollable widget. This
	 * effect can then be activated using
	 * {@link ScrollingSmoother#smoothControl(boolean)}.
	 * 
	 * @see ScrollingSmoother#smoothControl(boolean)
	 * 
	 * @param c2
	 * @param movement
	 * @param duration
	 */
	public ScrollingSmoother(final Scrollable c2, IMovement movement,
			int duration) {
		this(c2, movement);
		this.duration = duration;
	}

	/**
	 * Get current effect duration (ms).
	 * 
	 * @return
	 */
	public int getDuration() {
		return duration;
	}

	/**
	 * Set effect duration (ms).
	 * 
	 * @param duration
	 */
	public void setDuration(int duration) {
		this.duration = duration;
	}

	/**
	 * Set the FPS (frame per second) to use with the animator.
	 * 
	 * @param fps
	 */
	public void setFPS(int fps) {
		animationRunner = new AnimationRunner(fps);
	}

	protected ScrollBar getScrollbar(Event event) {
		ScrollBar result = verticalScrollBar;

		if (result == null) {
			result = horizontalScrollBar;
		}

		return result;
	}

	Listener mouseWheelListener = event -> {
		// Remove standard behavior
		event.doit = false;

		// Get scrollbar on which the event occurred.
		ScrollBar currentScrollBar = getScrollbar(event);

		int start = currentScrollBar.getSelection();
		int end = start;

		// If an effect is currently running, get the current and target
		// values.
		IEffect current = animationRunner.getEffect();
		if (current instanceof MoveScrollBarEffect) {
			MoveScrollBarEffect mseffect = (MoveScrollBarEffect) current;
			start = mseffect.getCurrent();
			end = mseffect.getEnd();
		}

		end -= event.count * currentScrollBar.getIncrement();

		if (end > currentScrollBar.getMaximum() - currentScrollBar.getThumb()) {
			end = currentScrollBar.getMaximum() - currentScrollBar.getThumb();
		}

		if (end < currentScrollBar.getMinimum()) {
			end = currentScrollBar.getMinimum();
		}

		animationRunner.runEffect(new MoveScrollBarEffect(currentScrollBar,
				start, end, duration, movement, null, null));
	};

	/**
	 * Enable or disable scrolling effect.
	 * 
	 * @param enable
	 *            true or false.
	 */
	public void smoothControl(boolean enable) {
		if (enable) {
			component.addListener(SWT.MouseWheel, mouseWheelListener);

			if (verticalScrollBar != null)
				verticalScrollBar.addListener(SWT.Selection,
						cancelEffectIfUserSelection);

			if (horizontalScrollBar != null)
				horizontalScrollBar.addListener(SWT.Selection,
						cancelEffectIfUserSelection);

		} else {
			component.removeListener(SWT.MouseWheel, mouseWheelListener);

			if (verticalScrollBar != null)
				verticalScrollBar.removeListener(SWT.Selection,
						cancelEffectIfUserSelection);

			if (horizontalScrollBar != null)
				horizontalScrollBar.removeListener(SWT.Selection,
						cancelEffectIfUserSelection);

		}
	}

	Listener cancelEffectIfUserSelection = e -> {
		// data contains an instance of MoveScrollBarEffect if the selection
		// was generated by this effect. Otherwise this is an user
		// selection.
		if (!(e.data instanceof MoveScrollBarEffect)) {
			animationRunner.cancel();
		}
	};

}
